#include <stdio.h>
//自定义类型：结构体
//1️⃣结构体类型的声明
//①结构的声明，结构体变量的创建和初始化 见P36~37  vscode 9.c
//②结构的特殊声明:匿名结构体类型
//若没有对结构体类型重命名，基本上只能使用一次。
struct
{
    int a;
    char b;
    double c;
}x,y,z;//第6行的意思是说要想用这个匿名结构体创建变量，只能在这直接创建，下面无法再用这个匿名结构体创建变量了。
struct
{
    int a;
    char b;
    double c;
} *p;
int main() 
{
    p=&x;//这是错的，上面两个匿名结构体虽然成员变量一样，但编译器会把上面的两个声明当成完全不同的两个类型，所以是非法的。
    return 0;
}
//结构体类型重命名 见vscode 19shunxvbiao.c
typedef struct    
{
    ElemType data[MAXSIZE]; 
    int length;  
}Sqlist; 
//③结构的自引用 见vscode 20danlianbiao.c
typedef struct node  //标签不可省略，因为在声明中要用到结构体类型，没有标签结构体类型不正确
{
    //数据域:存储的数据信息
    ElemType data;
    //指针域:指向后继的地址
    struct node * next;  //next是一个struct node *类型的指针变量
}Node;
//2️⃣结构体内存对齐 ———— 计算结构体大小问题
//①引入：
struct S1
{
    char c1; //1
    char c2; //1
    int n;   //4
};
struct S2
{
    char c1; //1
    int n;   //4
    char c2; //1
};
int main()
{
    printf("%zu\n",sizeof(struct S1));//输出：8
    printf("%zu\n",sizeof(struct S2));//输出：12
    return 0;
}
//疑问：为什么成员变量一样，只是顺序不一样，结构体的大小就不一样？
//为了解决这个疑问，我们先学习一个宏：offsetof(type,member)  头文件：<stddef.h>
//参数：type——结构体类型；member——结构体成员名字
//返回值：size_t类型，结构体成员相对于结构体起始位置的偏移量。
//分析： 
#include <stddef.h>
struct S2
{
    char c1; //1
    int n;   //4
    char c2; //1
};
int main()
{
    struct S2 s2;
    printf("%zu\n",offsetof(struct S2,c1));//输出：0   说明从结构体起始位置开始，一个字节存放c1
    printf("%zu\n",offsetof(struct S2,n)); //输出：4   说明从偏移量为4的位置开始，四个字节存放n
    printf("%zu\n",offsetof(struct S2,c2));//输出：8   说明从偏移量为8的位置开始，一个字节存放c2
    return 0;
}
//从上面代码可以看出，从结构体起始位置一字节存c1，接着浪费三字节（空着），接着四字节存n，接着一字节存c2，接着浪费三字节（为了凑够12字节）。
//这么存储是按照对齐规则来的。P110
//②对齐规则例题：P111
//例题1
struct S1
{
    char c1;
    int i;
    char c2;
};
//例题2
struct S2
{
    char c1;
    char c2;
    int i;
};
//例题3
struct S3
{
    double d;
    char c;
    int i;
};
//例题4-结构体嵌套问题
struct S4
{
    char c1;
    struct S3 s3;
    double d;
};
int main()
{
    struct S1 s1;
    struct S2 s2;
    struct S3 s3;
    struct S4 s4;
    printf("%d\n", sizeof(struct S1));//12
    printf("%d\n", sizeof(struct S2));//8
    printf("%d\n", sizeof(struct S3));//16
    printf("%d\n", sizeof(struct S4));//32
    printf("%d\n", sizeof(s4.s3));//16  嵌套的结构体的大小和它独立存在时的大小一样
    return 0;
}
//结构体的大小是由其成员变量所占用的内存空间总和决定的，只要这些成员变量的定义不变，结构体自身的大小就不会改变，无论它是独立存在还是被嵌套在其他结构体中。
struct S2
{
    char c2;//1
    int i;//4
};
struct S3
{
    short a;//2
    double b;//8
};
struct S4
{
    struct S2 s2;
    struct S3 s3;
};
int main()
{
    struct S2 s2;
    struct S3 s3;
    struct S4 s4;
    printf("%d\n", sizeof(struct S2));//8
    printf("%d\n", sizeof(struct S3));//16
    printf("%d\n", sizeof(struct S4));//24
    printf("%d\n", sizeof(s4.s2));//8
    printf("%d\n", sizeof(s4.s3));//16
    return 0;
}
//③修改默认对齐数：#pragma 这个预处理指令，可以改变编译器的默认对齐数
#pragma pack(1)//设置默认对齐数为1 （一般设置为2的次方数）
struct S
{
    char c1;//1   1   1
    int i;  //4   1   1
    char c2;//1   1   1
};
#pragma pack()//取消设置的对齐数，还原为默认对齐数
int main()
{
    printf("%zu\n", sizeof(struct S));//6
    return 0;
}
//3️⃣结构体传参  见vscode 9.c
//4️⃣结构体实现位段
//①位段与结构的对比：
//结构的声明：
struct A
{
    int _a;
    int _b;
    int _c;
    int _d;
};
//位段的声明：
struct B
{
    int _a:2;//这个数字表示这个成员要占用的比特位的数量。
    int _b:5;//_b占5个比特位
    int _c:10;
    int _d:30;
};
int main()
{
    struct A a;
    struct B b;
    printf("%zu\n",sizeof(struct A));//16
    printf("%zu\n",sizeof(struct B));//8   解析:P114
    return 0;
}
//②位段的内存分配：
//例题：P113
struct A
{
    char a:3;
    char b:4;
    char c:5;
    char d:4;
};
int main()
{
    struct A s={0};
    s.a=10;
    s.b=12;
    s.c=3;
    s.d=4;
    printf("%zu\n",sizeof(struct A));//3
    return 0;
}
//③位段使用的注意事项：
//位段的几个成员可能共用一个字节，这样有些成员的起始位置并不是某个字节的起始位置，那么这些位置处是没有地址的。
//内存中每个字节分配一个地址，一个字节内部的bit位是没有地址的。
//所以不能对位段的成员使用&操作符，这样就不能使用scanf直接给位段的成员输入值，只能是先输入放在一个变量中，然后赋值给位段的成员。
struct A
{
    int _a : 2;
    int _b : 5;
    int _c : 10;
    int _d : 30;
};
int main()
{
    struct A sa = {0};
    //scanf("%d", &sa._b);//这是错误的
    //正确的示范：
    int b = 0;
    scanf("%d",&b);
    sa._b = b;
    return 0;
}

